package domain;

import java.io.Serializable;
import java.util.Set;
import java.util.ArrayList;
import java.util.HashMap;

/**
 * @author Carlos García Márquez
 * @author Andreu Marimon Aguadé
 * @author Brian Carvajal Meza
 */
class TrieNode<T> implements Serializable {

    protected String _label;
    protected T _value;
    protected boolean _isFinal;
    protected HashMap<String, TrieNode<T>> _children;
    protected boolean _isRoot;
    protected int _depth;

    /**
     * Crea un {@code TrieTrieNode} de tipus arrel buit.
     */
    protected TrieNode() {
        _label = "";
        _value = null;
        _isFinal = false;
        _children = new HashMap<String, TrieNode<T>>();
        _isRoot = true;
        _depth = 0;
    }

    /**
     * Crea un {@code TrieNode) de tipus arrel amb el valor donat.
     * @param value Valor del nou {@code TrieNode}.
     */
    protected TrieNode(T value) {
        this();
        _value = value;
    }

    /**
     * Crea un {@code TrieNode} normal amb els paràmetres indicats.
     * @param label Etiqueta del node.
     * @param depth Profunditat del node.
     */
    protected TrieNode(String label, int depth) {
        this();
        _label = label;
        _isRoot = false;
        _depth = depth;
    }

    /**
     * Afegeix el valor T sota la clau {@code path}.
     * Si {@code path} ja te un valor assignat el sobreescriu.
     * @param path direcció relativa del node a partir de l'actual.
     * @param value valor que s'ha de guardar.
     * @param isFinal indica si el node s'ha de marcar com a final o no.
     */
    private void set(String path, T value, Boolean isFinal) {
        if (path.length() == 0) {
            _value = value;
            _isFinal = isFinal;
        } else {
            String label = path.substring(0, 1);
            path = path.substring(1);
            TrieNode<T> child = _children.get(label);
            if (child == null) {
                child = new TrieNode<T>(label, _depth + 1);
                _children.put(label, child);
            }
            child.set(path, value, isFinal);
        }
    }

    /**
     * Afegeix el valor T sota la clau {@code path} i el marca com a final.
     * Si {@code path} ja te un valor assignat el sobreescriu.
     * @param path direcció relativa del node a partir de l'actual.
     * @param value valor que s'ha de guardar.
     */
    protected void put(String path, T value) {
        this.set(path, value, true);
    }

    /**
     * Afegeix el valor T sota la clau {@code path} pero no el marca com a final.
     * Si {@code path} ja te un valor assignat el sobreescriu.
     * @param path direcció relativa del node a partir de l'actual.
     * @param value valor que s'ha de guardar.
     */
    protected void assign(String path, T value) {
        this.set(path, value, false);
    }

    /**
     * Elimina el {@code TrieNode} corresponent.
     * @param path direcció relativa del node que s'ha d'eliminar.
     */
    protected void delete(String path) {
        if (path.length() == 0) {
            _value = null;
            _isFinal = false;
        } else {
            String label = path.substring(0, 1);
            path = path.substring(1);
            TrieNode<T> child = _children.get(label);
            if (child != null) {
                child.delete(path);
            }
        }
    }

    /**
     * @return Retorna {@code true} si el {@code TrieNode} és final.
     */
    protected boolean isFinal() {
        return _isFinal && !_isRoot;
    }

    /**
     * @return Retorna {@code true} si el {@code TrieNode} és arrel.
     */
    protected boolean isRoot() {
        return _isRoot;
    }

    /**
     * @return Retorna l'etiqueta del node
     */
    protected String getLabel() {
        return _label;
    }

    /**
     * @return Retorna el valor T guardat al {@code TrieNode}.
     */
    protected T getValue() {
        return _value;
    }

    /**
     * Obté el node que penja del actual amb clau {@code path}.
     * @param path Ruta relativa del {@code TrieNode}.
     * @return Retorna el node si existeix, {@code null} altrement.
     */
    protected TrieNode<T> getNode(String path) {
        if (path.length() == 0) {
            return this;
        } else {
            String label = path.substring(0, 1);
            path = path.substring(1);
            TrieNode<T> child = _children.get(label);
            return (child == null) ? null : child.getNode(path);
        }
    }

    /**
     * Obté el valor sota el node actual que te clau {@code path}.
     * @param path Ruta relativa del {@code TrieNode}.
     * @return Retorna el valor {@code T} si existeix, {@code null} altrement.
     */
    protected T get(String path) {
        TrieNode<T> target = this.getNode(path);
        return (target == null) ? null : target.getValue();
    }

    /**
     * Obté tots els nodes que son fills directes del actual.
     * @return Retorna la llista de nodes, o una llista buida si no n'hi ha cap.
     */
    protected ArrayList<TrieNode<T>> getChildNodes() {
        ArrayList<TrieNode<T>> nodeList = new ArrayList<TrieNode<T>>();
        Set<String> keySet = _children.keySet();
        for (String key : keySet) {
            nodeList.add(_children.get(key));
        }
        return nodeList;
    }

    /**
     * Obté tots els nodes que penjen del actual i ell mateix.
     * @return Retorna la llista de nodes, o una llista buida si no n'hi ha cap.
     */
    protected ArrayList<TrieNode<T>> getAllNodes() {
        ArrayList<TrieNode<T>> nodeList = new ArrayList<TrieNode<T>>();
        Set<String> keySet = _children.keySet();
        for (String key : keySet) {
            TrieNode<T> child = _children.get(key);
            nodeList.addAll(child.getAllNodes());
        }
        nodeList.add(this);
        return nodeList;
    }

    /**
     * Obté tots els valors dels nodes que penjen del actual i l'actual.
     * @return Llista de {@code T}, o una llista buida si no n'hi ha cap.
     */
    protected ArrayList<T> getAll() {
        ArrayList<T> valueList = new ArrayList<T>();
        Set<String> keySet = _children.keySet();
        for (String key : keySet) {
            TrieNode<T> child = _children.get(key);
            valueList.addAll(child.getAll());
        }
        if (_value != null) {
            valueList.add(_value);
        }
        return valueList;
    }

    /**
     * @return Retorna la representació com a String de TrieNode
     */
    @Override
    public String toString() {
        String nodeString = "";
        if (_isRoot) {
            nodeString += "Root {}\n";
        } else {
            for (int i = 0; i < _depth; i++) {
                nodeString += ".";
            }
            if (_isFinal) {
                nodeString += " TrieNode {" + _label + "}*\n";
                nodeString += "_value:(" + _value.getClass() + ") =\n" + _value.toString() + "\n";
            } else {
                nodeString += " TrieNode {" + _label + "}\n";
            }
        }
        Set<String> keySet = _children.keySet();
        for (String key : keySet) {
            TrieNode<T> child = _children.get(key);
            nodeString += child.toString();
        }
        return nodeString;
    }
}